#include <config.h>
#include "HL2MasterServer.h"
#include "HL2ServerInfo.h"

#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>

#include <stdexcept>
#include <iostream>

#include <compat.h>

#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

extern int h_errno;


using namespace std;

HL2MasterServer::HL2MasterServer(const char *_addr, int port)
{
	char buf[BUFSIZ] = "";
	struct hostent he = {};
	int herrno = 0;
	struct hostent *result = NULL;

	servers = new stack<ServerInfo *>;

	memset(&addr, 0, sizeof(struct sockaddr_in));
	this->addr.sin_family = AF_INET;
	this->addr.sin_port = htons(port);

	if(gethostbyname_r(_addr, &he, buf, BUFSIZ, &result, &herrno) == 0) {
		memcpy(&this->addr.sin_addr.s_addr, he.h_addr_list[0], he.h_length);
	} else {
		throw invalid_argument("Unabe to resolve hostname");
	}

}

HL2MasterServer::HL2MasterServer(HL2MasterServer &arg)
{
	memcpy(&addr, arg.getAddr(), sizeof(struct sockaddr_in));
}

HL2MasterServer::~HL2MasterServer()
{
//stack<ServerInfo *> servers;
	this->clearList();
	delete(servers);
}

const struct sockaddr_in *HL2MasterServer::getAddr() const
{
	return &addr;
}

stack<ServerInfo *> *HL2MasterServer::list()
{
	this->list(STEAM_REGION_US_EC);
	this->list(STEAM_REGION_US_WC);
	this->list(STEAM_REGION_SA);
	this->list(STEAM_REGION_EU);
	this->list(STEAM_REGION_AS);
	this->list(STEAM_REGION_AUS);
	this->list(STEAM_REGION_ME);
	this->list(STEAM_REGION_AF);
	return this->list(STEAM_REGION_OTHER);
}

ServerInfo *HL2MasterServer::getServerInfo(const char *_addr, int port)
{
	HL2ServerInfo *info = new HL2ServerInfo(_addr, port);
	return info;
}

stack<ServerInfo *> *HL2MasterServer::list(enum STEAM_REGION reg)
{
	int s = socket(AF_INET, SOCK_DGRAM, 0);
	struct sockaddr_in laddr = {};
	struct sockaddr_in faddr = {};
	socklen_t flen = sizeof(struct sockaddr_in);
	char firstreq[] = "\x31" "\0" "0.0.0.0:0" "\0" "\\type\\d\0\\proxy\\0\0" ;// "\\secure\\1\0" "\\type\\d\0";// "\\proxy\\0\0";
	char buf[BUFSIZ];
	int r;
	if(s<0) {
		throw runtime_error(strerror(errno));
	}

	firstreq[1] = reg;

	laddr.sin_family = AF_INET;
	bind(s, (struct sockaddr *)&laddr, sizeof(struct sockaddr_in));
	//connect(s, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));

	memset(buf, 0, sizeof(buf));
	sendto(s, firstreq, sizeof(firstreq), 0, (struct sockaddr *)&addr, sizeof(struct sockaddr));

	// XXX: select
	// Retry
	do {
		struct timeval tv;
		int sr;
		fd_set rset, eset;
		FD_ZERO(&rset);
		FD_ZERO(&eset);
		FD_SET(s, &rset);
		FD_SET(s, &eset);
		tv.tv_sec = 5;
		tv.tv_usec = 0;
		sr = select(s+1, &rset, NULL, &eset, &tv);
		if(sr<=0) {
			// Retry!
			break;
		}
		if(FD_ISSET(s, &rset) && (r = recvfrom(s, buf, sizeof(buf), 0, (struct sockaddr *)&faddr, &flen))>0) {
			if(!memcmp("\xff\xff\xff\xff\x66\x0a", buf, 6) && r > 6) {
				int pos;
				char address[256] = "";
				unsigned short *p = NULL;
				for(pos = 6;pos < r; pos+=6) {
					snprintf(address, sizeof(address), "%u.%u.%u.%u", 
							0xff & buf[pos], 0xff & buf[pos+1], 0xff & buf[pos+2], 0xff & buf[pos+3]);
					p = (unsigned short *)& buf[pos+4];
					cerr << "Got server at " << address << ":" << ntohs(*(p)) << endl;
					if(!strncmp(address, "0.0.0.0", 7)) {
						break;
					}
					//HL2ServerInfo *info = new HL2ServerInfo((char *)address, ntohs(*(p)));
					servers->push(this->getServerInfo(address, ntohs(*p)));
					//delete(info);
				}
				if(!strcmp(address, "0.0.0.0")) {
					// last server
					break;
				}

				pos-=6;
				memset(address, 0, sizeof(address));
				snprintf(address, sizeof(address), "%u.%u.%u.%u:%u", 
						0xff & buf[pos], 0xff & buf[pos+1], 0xff & buf[pos+2], 0xff & buf[pos+3], ntohs(*(p)));

				memset(buf, 0, sizeof(buf));
				int i=0;
				buf[i++] = 0x31;
				buf[i++] = reg;
				for(int c=0;address[c];c++) {
					buf[i++] = address[c];
				}
				buf[i++] = 0;
				buf[i++] = 0;
				sendto(s, buf, i, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr));
			} else {
				cerr << "Error getting reply from master" << endl;
				break;
			}
		} else {
			perror("recvfrom");
			break;
		}
	} while(1);

	close(s);
	return servers;
}


HL2MasterServer &HL2MasterServer::operator=(const HL2MasterServer &arg)
{
	memcpy(&addr, arg.getAddr(), sizeof(struct sockaddr_in));
	return *this;
}

void HL2MasterServer::clearList()
{
	while(!servers->empty()) {
		ServerInfo *s;
		s = servers->top();
		servers->pop();
		delete(s);
	}
}
